#pragma once
#include <QAbstractSocket>
#include <QSharedPointer>
#include <QtEndian>

#include "payload.h"

namespace apollo
{
	namespace core
	{

		/*Protocol:
		1. magic string (6 bytes) = #!X8wz
2. header length (2 bytes)
3. message id (4 bytes)
4. message type (2 bytes)
5. header (key value pair with \n)
6. body length (4 bytes)
7. body
8. crc-16 (4 bytes)
9. terminator string (6 bytes) = #!zw8X

		endian: bigendian
		header coding: utf8
		checksum algorithm: crc-16
		*/
		
		class WireFormat
		{
		public:
			static const int BODY_SIZE_FIELD = 4;
			static const int MESSAGE_ID_FIELD = 4;
			static const int MESSAGE_TYPE_FIELD = 2;
			static const int HEADER_SIZE_FIELD = 2;
			static const int CHECKSUM_FIELD = 4;
			static const int MAGIC_STRING_LENGTH = 6;
			static const int TERMINATOR_LENGTH = 6;

			static  char MAGIC_STRING[7];//= "#!X8wz";
			static  char TERMINATOR[7];//= "#!zw8X";

			static const int MAX_HEADER_SIZE = 32767;
			static const int MAX_BODY_SIZE = 10 * 1024 * 1024;

		public:
			enum ReadState{Successful = 0, NoAvailabeBytesError = 1001, ChecksumError, MessageSizeError, 
				MagicStringError, TerminatorError, InvalidSocket, Unknown};
			enum ReadStep {Begin, HeaderSize, Header, BodySize, Body, Checksum, End};
			class ReadingContext 
			{
			public:
				quint16 headerSize;
				qint32 bodySize;
				qint32 messageId;
				qint16 messageType;
				QString header;
				qint32 currentBodySize;
				qint32 checksum;

				char* body;
				ReadStep readedStep;
				WireFormat::ReadState state;
				ReadingContext():headerSize(0),body(NULL), bodySize(0),messageId(0), messageType(0),readedStep(WireFormat::Begin), state(WireFormat::Unknown){}
				void createBody(qint32 size)
				{
					if(body != NULL)
					{
						delete body;
					}
					body = new char[size];
				}
				~ReadingContext(){ if( body!=NULL ) delete body;}
			};

			
		private:
			int readTimeout;
			static const int DEFAULT_READ_TIMEOUT = 30000; //ms
		private:
			bool IsReadReady(QSharedPointer<QAbstractSocket> socket,int length, int timeout);
		public:
			explicit WireFormat(int readTimeout = DEFAULT_READ_TIMEOUT);
			~WireFormat(void);

			PayloadPtr unblockingRead(QSharedPointer<QAbstractSocket> socket, WireFormat::ReadingContext &cxt);
			ReadState read(QSharedPointer<QAbstractSocket> socket, PayloadPtr payload);
			void decode(char* data, qint32 dataSize, PayloadPtr payload, quint16 &checksum);
			void write(QSharedPointer<QAbstractSocket> socket, PayloadPtr payload);
			void encode(std::string & data, PayloadPtr payload);

		};
	}}
